from typing import List, Union
import sys

class Student:
    '学生类，表示一个学生实体'

    def __init__(self, id:str, name:str, age:int, gender:str, score:int) -> None:
        self.id = id
        self.name = name
        self.age = age
        self.gender = gender
        self.score = score

    def __str__(self):
        """返回学生信息的字符串表示"""
        return f"{self.id}\t{self.name}\t{self.age}\t{self.gender}\t{self.score}"
    
    def to_list(self):
        return [
            str(self.id),      
            self.name,
            str(self.age),     
            self.gender,
            str(self.score)    
        ]
    
    def to_dict(self) -> dict:
        #返回字典形式的学生对象
        return {
            'id':self.id,
            'name':self.name,
            'age':self.age,
            'gender':self.gender,
            'score':self.score
        }
    
    @classmethod
    def from_dict(cls, data):
        #从字典创建学生对象
        return cls(
            data["student_id"],
            data["name"],
            data["age"],
            data["gender"],
            data["score"]
        )

class StudentManager:
    #学生管理器
    def __init__(self):        
        self.students = []
    
    def add(self, student:Student) -> bool:
        #添加学生，返回是否添加成功
        for s in self.students:
            if s.id == student.id:
                return False
        
        self.students.append(student)
        return True
    
    def delete(self, id:str) -> bool:
        #删除学生，返回是否删除成功
        s = self.search(id=id)
        if type(s) == Student:
            self.students.remove(s)
            return True
        return False

    def update(self, id:Union[str,bool]=False, new_student:Union[Student, dict, bool]=False) -> bool:
        '修改学生数据，可传入dict或者Student，返回是否修改成功'
        #dict中可以包含任意数量的数据,但如果没有id还不传入id则返回false
        match new_student:
            case Student():
                #若参数为Student
                student = self.search(id=new_student.id)

                if type(student) == Student:
                    #修改student数据
                    student.name = new_student.name
                    student.age = new_student.age
                    student.gender = new_student.gender
                    student.score = new_student.score
                    return True
                
                elif student == False:
                    #student为false代表找不到这个人
                    return False
                
                else:
                    return False
                
            case dict():
                #若参数为dict
                if id:
                    #若id不为False
                    student = self.search(id=id)
                    if type(student) == Student:
                        #修改student数据
                        if new_student.get('name', False) != False:
                            student.name = new_student['name']
                        if new_student.get('age', False) != False:    
                            student.age = new_student['age']
                        if new_student.get('gender', False) != False:
                            student.gender = new_student['gender']
                        if new_student.get('score', False) != False:
                            student.score = new_student['score']
                        return True
                             
                    elif student == False:
                        #student为false代表找不到这个人
                        return False
                    
                    else:
                        return False
                
                elif new_student.get('id', False) != False:
                    #说明dict包含id
                    student = self.search(id=new_student.get('id', False))

                    if type(student) == Student:
                        #修改student数据
                        if new_student.get('name', False) != False:
                            student.name = new_student['name']
                        if new_student.get('age', False) != False:    
                            student.age = new_student['age']
                        if new_student.get('gender', False) != False:
                            student.gender = new_student['gender']
                        if new_student.get('score', False) != False:
                            student.score = new_student['score']
                        return True
                             
                    elif student == False:
                        #student为false代表找不到这个人
                        return False
                    
                    else:
                        return False
                    
                else:
                    #既不传入id，dict也不包含id，返回false
                    return False
                    
            case bool():
                return False
            
        return False

    def search(self, 
               id:Union[str, bool]=False, 
               name:Union[str, bool]= False, 
               age:Union[int, bool]=False, 
               gender:Union[str, bool]=False, 
               score:Union[int, bool]=False) -> Union[Student, bool, List[Student]]:
        '''查找学生，可任意传入id、name、age、gender、score进行匹配
        查找成功则返回Student，找到多个则返回List[Student]，找不到则返回False'''
        
        if not(id or name or age or gender or score):
            #如果未传入参数则返回False
            return False
        
        if id != False:
            #根据id查询
            for s in self.students:
                if s.id == id:
                    return s
                
        elif name != False:
            #根据名字查询
            result:List[Student] = []
            for s in self.students:
                if (s.name == name 
                    and (s.age == age) if age != False else True
                    and (s.gender == gender) if gender != False else True
                    and (s.score == score) if score != False else True
                    #如有其他条件则分别匹配
                    ):
                    result.append(s)
            return result
        
        elif age != False:
            #根据年龄查询
            result:List[Student] = []
            for s in self.students:
                if (s.age == age 
                    and (s.gender == gender) if gender != False else True
                    and (s.score == score) if score != False else True
                    #如有其他条件则分别匹配
                    ):
                    result.append(s)
            return result
        
        elif gender != False:
            #根据性别查询
            result:List[Student] = []
            for s in self.students:
                if (s.gender == gender 
                    and (s.score == score) if score != False else True
                    #如有其他条件则分别匹配
                    ):
                    result.append(s)
            return result
        
        elif score != False:
            #根据成绩查询
            result:List[Student] = []
            for s in self.students:
                if s.score == score:
                    result.append(s)
            return result
        
        return False
        
    def list(self):
        #返回学生列表
        return self.students

    def statistics(self):
        """生成学生数据的统计信息
        返回包含以下内容的字典：
        - count: 学生总数
        - midian: 成绩中位数
        - average_score: 平均分（保留两位小数）
        - max_score: 最高分学生对象/列表
        - min_score: 最低分学生对象/列表
        """
        if self.students == []:
            # 空数据集返回默认值
            return {
                'count':0,
                'average_score':0.00,
                'max_score':0,
                'min_score':0
            }
        
        else:
            # 计算中位数逻辑
            sorted_students = sorted(self.students, key=lambda x:x.score)
            # 获取纯分数列表用于计算
            sorted_scores = [s.score for s in sorted_students]
            n = len(sorted_scores)
            
            if n % 2 == 1:
                # 奇数情况：取中间分数
                midian = sorted_scores[n//2]
            else:
                # 偶数情况：取中间两个分数的平均值
                midian = (sorted_scores[n//2 - 1] + sorted_scores[n//2]) / 2

            # 基础统计计算
            total_score = sum(sorted_scores)
            max_score = max(sorted_scores)
            min_score = min(sorted_scores)

            return {
                'count': len(self.students),
                'midian': midian,  # 修正后的中位数数值
                'average_score': round(total_score / len(self.students), 2),  # 保留两位小数
                'max_score': self.search(score=max_score),  # 返回学生对象/列表
                'min_score': self.search(score=min_score)   # 返回学生对象/列表
            }
        
class FileHandler:
    """CSV 文件处理器
    功能：
    1. 从CSV文件加载学生数据到内存
    2. 将内存中的学生数据保存到CSV文件
    """
    
    def __init__(self, filename):
        """初始化文件处理器
        参数：
        filename -- 默认数据文件路径（字符串）
        """
        self.filename = filename

    def load(self):
        """加载学生数据
        返回：
        List[Student] -- 成功时返回学生对象列表
        False -- 文件不存在或读取失败时返回
        """
        try:
            self.file = open(self.filename, 'r', encoding='utf-8')
        except:
            return False
        
        students = []
        for s in self.file:
            # 解析CSV行数据：id,name,age,gender,score
            data = s.split(',')
            students.append(Student(
                id=data[0],        # 学号字段（字符串）
                name=data[1],      # 姓名字段
                age=int(data[2]),  # 年龄字段转为整数
                gender=data[3],    # 性别字段
                score=int(data[4]) # 分数字段转为整数
            ))
        self.file.close()
        return students

    def save(self, students: List[Student], filename=None):
        """保存学生数据到CSV
        参数：
        students -- 要保存的学生对象列表
        filename -- 可选参数，指定保存路径（默认使用初始化路径）
        
        返回：
        True -- 保存成功
        False -- 保存失败（路径错误或权限问题）
        """
        if filename is None:
            filename = self.filename
        try:
            file = open(filename, 'w', encoding='utf-8')
        except:
            return False
        
        # 将每个学生对象转换为CSV行数据
        for s in students:
            # 使用学生对象的to_list方法获取字段列表
            file.write(','.join(s.to_list()) + '\n')
        file.close()
        return True



class Main:
    '''
    要实现的交互功能有：
    1.添加学生
    2.删除学生
    3.更新学生数据
    4.查找学生
    5.显示所有学生
    6.统计信息：学生总数、平均分、最高分、最低分、输出前x名、后x名并保存为csv
    7.将数据输出为csv表格（按id、分数、首字母排序）
    0.退出系统
    '''
    def __init__(self, filename) -> None:
        self.filename = filename
        self.manager = StudentManager()
        self.fileHandler = FileHandler(self.filename)

    def add(self):
        """处理添加学生流程
        步骤：
        1. 收集用户输入（id/姓名/年龄/性别/分数）
        2. 验证id唯一性
        3. 创建Student对象并持久化存储
        4. 实时保存到CSV文件
        """
        id = 0
        name = ''
        age = 0
        gender = ''
        score = 0
        print('添加学生：')
        print('请输入id,留空则取消：', end='')
        while True:
            if id != '':
                id = input()
                str_id = id
                try:
                    id = int(id)
                    break
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                return None
            
        print('请输入姓名：', end='')
        name = input()

        print('请输入年龄：', end='')
        while True:
            age = input()
            try:
                age = int(age)
                break
            except:
                print('输入有误！请重新输入：', end='')
                continue
            
        print('请输入性别：', end='')
        gender = input()

        print('请输入分数：', end='')
        while True:
            score = input()
            try:
                score = int(score)
                break
            except:
                print('输入有误！请重新输入：', end='')
                continue
        
        success = self.manager.add(Student(id=str_id,name=name,age=age,gender=gender,score=score))
        if success:
            self.fileHandler.save(self.manager.students)
            print('添加成功！', end='')
            input('按回车继续')
            return None
        else:
            print('添加失败！学生已存在！')
            input('按回车继续')
        
    def delete(self):
        """处理删除学生流程
        逻辑：
        1. 通过id定位学生
        2. 执行删除前验证学生存在
        3. 删除成功后更新CSV文件
        """
        id = 0
        print('删除学生：')
        print('请输入id,留空则取消：', end='')
        while True:
            if id != '':
                id = input()
                str_id = id
                try:
                    id = int(id)
                    break
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                return None

        success = self.manager.delete(str_id)
        if success:
            self.fileHandler.save(self.manager.students)
            print('删除成功！')
            input('按回车继续')
        else:
            print('删除失败！学生不存在！')
            input('按回车继续')

    def update(self):
        """处理更新学生信息
        特点：
        - 支持部分字段更新
        - 空输入保留原值
        - 实时保存修改到文件
        """
        id = 0
        name = False
        age = False
        gender = False
        score = False
        print('更新学生信息：')
        print('请输入id，留空则取消：', end='')
        while True:
            id = input()
            str_id = id
            if id != '':
                try:
                    id = int(id)
                    if self.manager.search(id=str_id):
                        break
                    else:
                        print('学生不存在！请重新输入：', end='')
                        continue
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                return None

        print('请输入要更改的信息，留空则不更改')

        print('姓名：', end='')
        name = input()
        if name == '':
            name = False

        print('年龄：', end='')
        while True:
            age = input()
            if age != '':
                try:
                    age = int(age)
                    break
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                age = False
                break
               
        print('性别：', end='')
        gender = input()
        if gender == '':
            gender = False

        print('分数：', end='')
        while True:
            score = input()
            if score != '':
                try:
                    score = int(score)
                    break
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                score = False
                break

        success = self.manager.update(
            id=str_id,
            new_student={
                'id':str_id,
                'name':name if name != False else False,
                'age':age if age != False else False,
                'gender':gender if gender != False else False,
                'score':score if score != False else False,
            }
        )
        if success:
            self.fileHandler.save(self.manager.students)
            print('修改成功！', end='')
            input('按回车继续')
        else:
            print('修改失败！学生不存在！')
            input('按回车继续')
        
    def search(self):
        """多条件组合查询系统
        查询方式：
        - 支持5个字段的任意组合查询
        - 精确匹配与列表返回机制
        - 交互式输入引导
        """
        id = False
        name = False
        age = False
        gender = False
        score = False
        print('查找学生：')
        print('请输入匹配条件，留空则跳过')
        print('id：', end='')
        while True:
            id = input()
            str_id = id
            if id != '':
                try:
                    id = int(id)
                    if student := self.manager.search(id=str_id):
                        print(student)
                        return None
                    else:
                        print('学生不存在！请重新输入：', end='')
                        continue
                except:
                    print('输入有误！q请重新输入：', end='')
                    continue
            else:
                id = False
                break
        
        print('姓名：', end='')
        name = input()
        if name == '':
            name = False

        print('年龄：', end='')
        while True:
            age = input()
            if age != '':
                try:
                    age = int(age)
                    break
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                age = False
                break
               
        print('性别：', end='')
        gender = input()
        if gender == '':
            gender = False

        print('分数：', end='')
        while True:
            score = input()
            if score != '':
                try:
                    score = int(score)
                    break
                except:
                    print('输入有误！请重新输入：', end='')
                    continue
            else:
                score = False
                break

        if id == False and name == False and age == False and gender == False and score == False:
            print('未输入任何数据，查找失败！')
            input()
            return None

        if result := self.manager.search(id=id,name=name,age=age,gender=gender,score=score):
            print('查询成功！')
            if type(result) == Student:
                print(result)
            elif isinstance(result, list):
                for i in result:
                    print(i)
            input('按回车继续')
            return None
        else:
            #result为false说明找不到
            print('未找到匹配的学生！', end='')
            input('按回车继续')
            
    def student_list(self):
        #列出所有学生
        for i in self.manager.students:
            print(i)
        input()

    def statistic_menu(self):
        """统计功能菜单界面
        提供7种统计维度选择：
        [1]学生总数 [2]平均分 [3]中位数
        [4]最高分 [5]最低分 [6]前x名 [7]倒数x名
        [0]返回主菜单
        """
        print('统计信息'.center(30, '-'))
        print('1.学生总数')
        print('2.平均分')
        print('3.中位数')
        print('4.最高分')
        print('5.最低分')
        print('6.前x名')
        print('7.倒数x名')
        print('0.返回菜单')
        print('输入对应数字进行操作:', end='')
        choice = input()
        if self.input_check(choice, (0,7)):
            return choice
        else:
            while True:
                print('输入有误！请重新输入:', end='')
                choice = input()
                if self.input_check(choice, (0,7)):
                    return choice
                else:
                    continue

    def statistic(self):
        """统计信息分发器
        根据用户选择调用对应的统计功能实现
        处理各种统计结果的展示逻辑
        """
        choice = self.statistic_menu()
        match choice:
            case '1':
                print('学生总数为：', len(self.manager.students))
                input('按回车继续')

            case '2':
                print('平均分为：{:.2f}'.format(self.manager.statistics()['average_score']))
                input('按回车继续')

            case '3':
                print('中位数为：{}'.format(self.manager.statistics()['midian']))
                input('按回车继续')

            case '4':
                result = self.manager.statistics()['max_score']
                print('最高分：')
                if type(result) == Student:
                    print(result)
                elif isinstance(result, list):
                    for i in result:
                        print(i)                
                input('按回车继续')

            case '5':
                result = self.manager.statistics()['min_score']
                print('最低分：')
                if type(result) == Student:
                    print(result)
                elif isinstance(result, list):
                    for i in result:
                        print(i)                
                input('按回车继续')

            case '6':
                print('x=',end='')
                while True:
                    x = input()
                    try:
                        x = int(x)
                        break
                    except:
                        print('输入有误！请重新输入:', end='')
                        continue
                sorted_students = sorted(self.manager.students, key=lambda x:x.score,reverse=True)
                result = sorted_students[:x]
                print('前{}名：'.format(x))
                for i in result:
                    print(i)
                input('按回车继续')

            case '7':
                print('x=',end='')
                while True:
                    x = input()
                    try:
                        x = int(x)
                        break
                    except:
                        print('输入有误！请重新输入:', end='')
                        continue
                sorted_students = sorted(self.manager.students, key=lambda x:x.score)
                result = sorted_students[:x]
                print('后{}名：'.format(x))
                for i in result:
                    print(i)
                input('按回车继续')

            case '0':
                return None
                
    def output_csv(self):
        """CSV导出核心逻辑
        功能流程：
        1. 选择排序字段（id/年龄/分数）
        2. 选择排序方向（正序/逆序）
        3. 输入自定义文件名
        4. 执行排序并保存文件
        """
        filename = self.filename
        students = self.manager.students
        sort = 1
        reversed = False
        print('保存为CSV：')
        print('排序依据：')
        print('1.id')
        print('2.年龄')
        print('3.分数')
        print('（留空默认为id）')
        print('请选择：',end='')
        while True:
            choice = input()
            if choice != '':
                if self.input_check(choice, (1,3)):
                    sort = int(choice)
                    break
                else:
                    while True:
                        print('输入有误！请重新输入:', end='')
                        choice = input()
                        if self.input_check(choice, (1,3)):
                            sort = int(choice)
                            break
            else:
                sort = 1
                break

        print('正序or逆序：')
        print('1.正序')
        print('2.逆序')
        print('（留空默认为正序）')
        print('请选择：',end='')
        while True:
            choice = input()
            if choice != '':
                if self.input_check(choice, (1,2)):
                    reversed = False if choice == '1' else True
                    break
                else:
                    while True:
                        print('输入有误！请重新输入:', end='')
                        choice = input()
                        if self.input_check(choice, (1,2)):
                            reversed = False if choice == '1' else True
                            break
            else:
                reversed = False
                break

        print('请输入文件名（包含扩展名.csv）:',end='')
        filename = input()
        match sort:
            case 1:
                students = sorted(students, key=lambda x:x.id, reverse=reversed)
            case 2:
                students = sorted(students, key=lambda x:x.age, reverse=reversed)
            case 3:
                students = sorted(students, key=lambda x:x.score, reverse=reversed)
        
        self.fileHandler.save(students=students, filename=filename)
        print('文件已保存至：',filename)
        input('按回车继续')

    def exit(self):
        """系统安全退出处理
        执行最后的数据保存：
        - 成功保存后正常退出
        - 保存失败提示错误信息
        """
        if self.fileHandler.save(self.manager.students, filename=self.filename):
            sys.exit()
        else:
            print('Error!文件保存失败！')
            input('按回车关闭')
            sys.exit(1)

    def main_menu(self):
        """主控制台菜单
        菜单项布局：
        - 功能选项1-7按操作频率排列
        - 退出选项单独放置最后
        - 菜单标题居中对齐
        """
        print('学生信息管理系统'.center(30, '-'))
        print('1.添加学生')
        print('2.删除学生')
        print('3.更新学生数据')
        print('4.查找学生')
        print('5.显示所有学生')
        print('6.统计信息')
        print('7.将数据输出为csv表格')
        print('0.退出系统')
        print('输入对应数字进行操作:', end='')
        choice = input()
        if self.input_check(choice, (0,7)):
            return choice
        else:
            while True:
                print('输入有误！请重新输入:', end='')
                choice = input()
                if self.input_check(choice, (0,7)):
                    return choice

    def input_check(self, input, tuple):
        """输入验证器
        参数：
        input - 需要验证的原始输入
        tuple - 有效范围元组（最小值，最大值）
        
        返回：
        bool - 是否为指定范围内的整数
        """
        try:
            input = int(input)
        except:
            return False
        if tuple[0] <= input <= tuple[1]:
            return True
        else:
            return False

    def main(self):
        """程序主控制流
        生命周期管理：
        1. 启动时加载数据文件
        2. 进入菜单主循环
        3. 根据选择调用功能模块
        4. 退出时自动持久化数据
        """
        if students := self.fileHandler.load():
            self.manager.students = students
        else:
            self.manager.students = []
        while True:
            choice = self.main_menu()
            match choice:
                case '1':
                    self.add()
                case '2':
                    self.delete()
                case '3':
                    self.update()
                case '4':
                    self.search()
                case '5':
                    self.student_list()
                case '6':
                    self.statistic()
                case '7':
                    self.output_csv()
                case '0':
                    self.exit()
   
if __name__ == '__main__':
    main = Main('students.csv')
    main.main()